use std::ops::Deref;

use axum::{routing::post, Router};
use bytes::BufMut;
use serde::Deserialize;
use tokio::fs;

use crate::{
    error::AppErr,
    global::APP,
    serialport::{self, cmd, BodyEncode},
    serve::utils::{Cbor, Void},
};

mod cargo;
mod drop;
mod heat;

struct OtaPkg<'a> {
    id: u16,
    data: &'a [u8],
}

impl<'a> BodyEncode for OtaPkg<'a> {
    fn encode(self, buf: &mut bytes::BytesMut) {
        buf.put_u16(self.id);
        buf.put_u8(self.data.len() as u8);
        buf.extend_from_slice(self.data);
    }
}

async fn ota(addr: u8, data: &[u8]) -> Result<(), AppErr> {
    let len = data.len() as u32;
    serialport::simple_req(addr, cmd::OTA_START, len)
        .await?
        .res_result()?;

    APP.notify_ota(&format!("擦除Flash成功"));

    let mut id = 0;
    let mut index = 0;
    while index < len {
        let mut n = len - index;
        if n > 220 {
            n = 220;
        }
        let data = &data[(index as usize)..((index + n) as usize)];
        let pkg = OtaPkg { id, data };
        serialport::simple_req(addr, cmd::OTA_TRANSLATE, pkg)
            .await?
            .res_result()?;
        id += 1;
        index += n;
        APP.notify_ota(&format!("进度:{}%", index * 100 / len));
    }

    APP.notify_ota(&format!("校验"));
    let md5_data = md5::compute(data);
    let m: &[u8] = md5_data.deref();
    serialport::simple_req(addr, cmd::OTA_COMPLETE, &m)
        .await?
        .res_result()?;

    Ok(())
}

#[derive(Deserialize)]
struct OtaReq {
    address: u8,
    path: String,
}

async fn on_ota_with_file(req: Cbor<OtaReq>) -> Result<Void, AppErr> {
    let data = fs::read(&req.path).await?;
    ota(req.address, &data).await?;
    Ok(Void)
}

#[derive(Deserialize)]
struct OtaDataReq {
    address: u8,
    #[serde(with = "serde_bytes")]
    data: Box<[u8]>,
}

async fn on_ota_with_data(req: Cbor<OtaDataReq>) -> Result<Void, AppErr> {
    ota(req.address, &req.data).await?;
    Ok(Void)
}

pub fn register() -> Router {
    Router::new()
        .route("/ota_with_file", post(on_ota_with_file))
        .route("/ota_with_data", post(on_ota_with_data))
        .nest("/cargo", cargo::register())
        .nest("/drop", drop::register())
        .nest("/heat", heat::register())
}


